package factory;

import client.ClientConfig;
import client.RpcClient;
import client.handle.ClientToCenterInboundHandle;
import error.IllegalCallException;
import error.NoConnectException;
import error.RemoteMethodNotFoundException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import support.Constans;
import support.RpcInfo;
import support.anno.Caller;
import support.anno.Config;
import util.AnnotaionScanner;
import util.StringUtil;
import util.WarpTool;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.List;

public class ProxyFactory {
    static Logger logger = LoggerFactory.getLogger(ProxyFactory.class);

    //为兼容spring boot starter
    static private String shost;
    static private Integer sport;

    static public void initHostAndPort(String a,Integer b){
        shost = a;
        sport = b;
    }

    //主要扫描是否启用注册中心配置
    static public void init(String scanPath) throws Exception {
        List<String> scannerList = AnnotaionScanner.scanner2(scanPath, Config.class);
        if(StringUtil.listIsEmpty(scannerList) && StringUtil.strIsEmpty(shost) && sport==null ){
            logger.info("找不到注册中心配置，使用点对点配置");
        }else {
            //准备初始化与注册中心的连接
            logger.info("开始与注册中心建立连接");
            if (StringUtil.listIsEmpty(scannerList)) {
                clientConfig = new ClientConfig(shost,sport);
            }else {
                String className = scannerList.get(0);
                Class<?> aClass = Class.forName(className);
                Config annotation = aClass.getAnnotation(Config.class);
                clientConfig = new ClientConfig(annotation.centerHostName(), annotation.centerPort());
            }
            RpcClient clientToCenter = new RpcClient(clientConfig.getCenterHost(),clientConfig.getCenterPort(),new ClientToCenterInboundHandle(Constans.CLIENT_FLAGE));
            RpcClientFactory.cache.put(Constans.CENTER_KEY,clientToCenter);
            try {
                clientToCenter.conn();
                logger.info("连接注册中心成功...");
            } catch (Exception e) {
                logger.error("与注册中心建立连接失败...");
                clientConfig =null;
                throw e;
            }
        }
    }

    static ClientConfig clientConfig;

    public static ClientConfig getClientConfig() {
        return clientConfig;
    }

    public static <T> T getProxy(Class<T> tClass, InvocationHandler handler){
        Object object = new Object();

        Object o = Proxy.newProxyInstance(tClass.getClassLoader(), new Class[]{tClass}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                if(isObjectMethod(method)){
                    logger.info("{}是Object的方法，不走代理",method.getName());
                    return method.invoke(object,args);
                }

                Class<?> declaringClass = method.getDeclaringClass();
                Caller annotation = declaringClass.getAnnotation(Caller.class);
                if(annotation == null){
                    throw new IllegalCallException("无注解元数据，非法调用...");
                }

                //弱配置了注册中心地址，则可以忽略这2行。
                String host = annotation.host();
                int port = annotation.port();

                RpcInfo sendInfo = WarpTool.warpInfo(declaringClass, method, args); //包装发送的信息
                //根据配置决定是否需要走注册中心，注册中心只是根据提供的信息去找哪个主机上暴露了该接口
                //流程:拿调用方法信息去中心找提供了该服务的主机地址，然后再向这个地址发起调用请求，这样的好处时调用方不需要知道哪个地址提供了该服务。
                if(clientConfig!=null){
                    //向注册中心根据com.xx.Iname + method找对应提供服务的主机地址
                    sendInfo.setCommand(1); //调用请求
                    RpcInfo centerRes = RpcClientFactory.cache.get(Constans.CENTER_KEY).sendToCenter(sendInfo);
                    if(centerRes==null) throw new NoConnectException("与远程服务连接丢失...");
                    if(centerRes.getErrorCode()!=null) throw new RemoteMethodNotFoundException("调用失败，详细原因:" + centerRes.getErrorCode());

                    //重新设置注册中心返回的服务提供方主机地址
                    host = centerRes.getHost();
                    port = centerRes.getPort();
                }

                String iname = annotation.share()?null:declaringClass.getName();
                RpcClient client = RpcClientFactory.getClient(host, port,iname);
                if(client == null) throw new NoConnectException("没有连接到远程主机...");

                logger.info("数据准备完毕，开始发送,发送内容{}...",sendInfo);

                RpcInfo rev = client.send(sendInfo);
                if(rev==null) throw new NoConnectException("与远程服务连接丢失...");
                if(rev.getErrorCode()!=null) throw new RemoteMethodNotFoundException("调用失败，详细原因:" + rev.getErrorCode());

                logger.info("调用远程方法成功...");
                return rev.getCallRes();
            }
        });
        return (T) o;
    }

    private static boolean isObjectMethod(Method method){
        Method[] methods = Object.class.getMethods();
        for(Method i:methods){
            if(i.equals(method)) return true;
        }
        return false;
    }

}
